package br.ufpe.cin.imlm2.jbeat.junite;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.junit.Test;

import br.cin.ufpe.junite.JunitETestCase;

import com.sun.codemodel.ClassType;
import com.sun.codemodel.JArray;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;

public class JUnitETestCaseWriter {

	private static final String METHOD_INTERMEDIATE_SITE = "intermediateSite";
	private static final String VARIABLE_TRACE = "trace";
	private static final String METHOD_EXCEPTION = "exception";
	private static final String METHOD_RAISE_SITE = "raiseSite";
	private static final String METHOD_CATCH_SITE = "catchSite";
	private static final String METHOD_SET_TRACE = "setExceptionPath";
	private JCodeModel model;
	private JDefinedClass testCaseClass;
	private File sourcesDir;
	private File resourcesDir;

	public JUnitETestCaseWriter( String fullyQualifiedClassName, String sourcesDir,
			String resourcesDir ) throws TestCaseWriterException {
		super();
		this.model = new JCodeModel();
		initClass( fullyQualifiedClassName );
		initDirs( sourcesDir, resourcesDir );
	}

	/**
	 * @param sourcesDir
	 * @param resourcesDir
	 * @throws TestCaseWriterException
	 */
	protected void initDirs( String sourcesDir, String resourcesDir )
			throws TestCaseWriterException {
		this.sourcesDir = new File( sourcesDir );
		if ( this.sourcesDir.exists() && !this.sourcesDir.isDirectory() )
			throw new TestCaseWriterException( "Source " + sourcesDir + " is not a directory" );
		if ( resourcesDir != null ) {
			this.resourcesDir = new File( resourcesDir );
			if ( this.resourcesDir.exists() && !this.resourcesDir.isDirectory() )
				throw new TestCaseWriterException( "Resource " + resourcesDir
						+ " is not a directory" );
		}
	}

	/**
	 * @param fullyQualifiedClassName
	 * @throws JClassAlreadyExistsException
	 */
	protected void initClass( String fullyQualifiedClassName ) throws TestCaseWriterException {
		try {
			this.testCaseClass = this.model._class( fullyQualifiedClassName, ClassType.CLASS );
			this.testCaseClass._extends( JunitETestCase.class );
		} catch ( JClassAlreadyExistsException e ) {
			throw new TestCaseWriterException( "Exception initializing JUnitTestCase class.", e );
		}
	}

	public void addTestMethodStub( String methodName, String exception, List<String> path )
			throws TestCaseWriterException {
		if ( path.size() < 1 ) {
			throw new TestCaseWriterException( "Path length must be >= 1" );
		}
		JBlock block = createMethodStub(methodName);
		String raiseSite = path.get( 0 );

		JArray traceInitializer = JExpr.newArray( this.model._ref( String.class ) );
		traceInitializer.add( JExpr.invoke( METHOD_EXCEPTION ).arg( exception ) );
		traceInitializer.add( JExpr.invoke( METHOD_RAISE_SITE ).arg( raiseSite ) );
		if(path.size() > 1) {
			for ( int i = 1; i < path.size() - 1; i++ ) {
				traceInitializer.add( JExpr.invoke( METHOD_INTERMEDIATE_SITE ).arg( path.get( i ) ) );
			}
			String catchSite = path.get( path.size() - 1 );
			traceInitializer.add( JExpr.invoke( METHOD_CATCH_SITE ).arg( catchSite ) );
		}
		block.decl( this.model._ref( String[].class ), VARIABLE_TRACE, traceInitializer );
		block.invoke( METHOD_SET_TRACE ).arg( JExpr.direct( VARIABLE_TRACE ) );

	}

	private JBlock createMethodStub(String methodName) {
		JMethod stub = this.testCaseClass.method( JMod.PUBLIC, void.class, methodName );
		stub.annotate( Test.class );
		JBlock block = stub.body();
		return block;
	}

	public void write() throws IOException {
		if ( !this.sourcesDir.exists() ) {
			this.sourcesDir.mkdirs();
		}
		if ( this.resourcesDir != null ) {
			if ( !this.resourcesDir.exists() ) {
				this.resourcesDir.mkdirs();
			}
			this.model.build( this.sourcesDir, this.resourcesDir );
		} else {
			this.model.build( this.sourcesDir );
		}
	}

}
